package com.detech.sbmh.business.login.serviceimpl;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.detech.sbmh.base.constant.CacheConstants;
import com.detech.sbmh.base.constant.Constants;
import com.detech.sbmh.base.enumtype.CodeEnum;
import com.detech.sbmh.base.enumtype.MsgEnum;
import com.detech.sbmh.base.exception.ServiceException;
import com.detech.sbmh.base.hibernate.user.dao.TbSysLogininforDAO;
import com.detech.sbmh.base.hibernate.user.dao.TbUserDAO;
import com.detech.sbmh.base.hibernate.user.entity.TbSysLogininfor;
import com.detech.sbmh.base.hibernate.user.entity.TbUser;
import com.detech.sbmh.base.redis.RedisUtils;
import com.detech.sbmh.base.satoken.core.service.PermissionService;
import com.detech.sbmh.base.satoken.enumtype.DeviceType;
import com.detech.sbmh.base.satoken.enumtype.UserStatus;
import com.detech.sbmh.base.satoken.model.LoginUser;
import com.detech.sbmh.base.satoken.properties.UserPasswordProperties;
import com.detech.sbmh.base.satoken.properties.WechatAppletProperties;
import com.detech.sbmh.base.satoken.utils.LoginHelper;
import com.detech.sbmh.base.util.ServletUtils;
import com.detech.sbmh.base.util.UserFun;
import com.detech.sbmh.business.login.service.LoginService;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Date;
import java.util.function.Supplier;

@Service
@Transactional
public class LoginServiceImpl implements LoginService {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Resource
    private TbUserDAO tbUserDAO;
    @Resource
    private TbSysLogininforDAO tbSysLogininforDAO;
    @Resource
    private PermissionService permissionService;
    @Autowired
    private UserPasswordProperties userPasswordProperties;
    @Autowired
    private WechatAppletProperties wechatAppletProperties;

    @Override
    public SaTokenInfo pcLogin(String username, String password) {
        //根据username获取用户信息
        LoginUser userInfo = getUserInfo(username, DeviceType.PC);
        //登录判断
        checkLogin(username, () -> !BCrypt.checkpw(password, userInfo.getPassword()),DeviceType.PC.getDevice());
        //写入登录信息
        LoginHelper.loginByDevice(userInfo, DeviceType.PC);
        //记录访问日志
        recordLogininfor(username, Constants.LOGIN_SUCCESS, MsgEnum.LOGIN_SUCCESS.getMsg(),DeviceType.PC.getDevice());
        //更新用户表中的最后登录时间和IP
        updateUserLoginInfo(userInfo.getId());
        return StpUtil.getTokenInfo();
    }

    @Override
    public SaTokenInfo appLogin(String username, String password) {
        //根据username获取用户信息
        LoginUser userInfo = getUserInfo(username,DeviceType.APP);
        //登录判断
        checkLogin(username, () -> !BCrypt.checkpw(password, userInfo.getPassword()),DeviceType.APP.getDevice());
        //写入登录信息
        LoginHelper.loginByDevice(userInfo, DeviceType.APP);
        //记录访问日志
        recordLogininfor(username, Constants.LOGIN_SUCCESS, MsgEnum.LOGIN_SUCCESS.getMsg(),DeviceType.APP.getDevice());
        //更新用户表中的最后登录时间和IP
        updateUserLoginInfo(userInfo.getId());
        return StpUtil.getTokenInfo();
    }

    @Override
    public SaTokenInfo xcxAutoLogin(String xcxCode) {
        //获取openid（微信用户身份唯一标识，也叫微信ID）
        String openid = getOpenid(xcxCode);
        //使用openid获取用户信息
        LoginUser userInfo = getUserInfoByOpenid(openid);
        // 生成token
        LoginHelper.loginByDevice(userInfo, DeviceType.XCX);
        //记录访问日志
        recordLogininfor(userInfo.getUsername(), Constants.LOGIN_SUCCESS, MsgEnum.LOGIN_SUCCESS.getMsg(),DeviceType.XCX.getDevice());
        //更新用户表中的最后登录时间和IP
        updateUserLoginInfo(userInfo.getId());
        return StpUtil.getTokenInfo();
    }

    @Override
    public SaTokenInfo xcxLogin(String xcxCode, String username, String password) {
        //获取openid（微信用户身份唯一标识，也叫微信ID）
        String openid = getOpenid(xcxCode);
        //使用openid获取用户信息
        LoginUser userInfo = getUserInfoByOpenid(openid);
        //登录判断
        checkLogin(username, () -> !BCrypt.checkpw(password, userInfo.getPassword()),DeviceType.XCX.getDevice());
        //写入登录信息
        LoginHelper.loginByDevice(userInfo, DeviceType.XCX);
        //记录访问日志
        recordLogininfor(username, Constants.LOGIN_SUCCESS, MsgEnum.LOGIN_SUCCESS.getMsg(),DeviceType.XCX.getDevice());
        //更新用户表中的最后登录时间和IP，并绑定openid
        updateUserLoginInfoAndOpenid(userInfo.getId(),openid);
        return StpUtil.getTokenInfo();
    }

    private LoginUser getUserInfoByOpenid(String openid) throws ServiceException {
        TbUser user = tbUserDAO.findByPropertyObject("openid",openid);
        if (ObjectUtil.isNull(user)) {
            throw new ServiceException(CodeEnum.authentication.getCode(),"用户不存在",openid);
        }
        if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
            throw new ServiceException("用户已被删除",openid);
        }
        if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            throw new ServiceException("用户已经停用",openid);
        }
        // 此处可根据登录用户的数据不同 自行创建 loginUser
        LoginUser loginUser = new LoginUser();
        loginUser.setId(user.getId());
        loginUser.setUsername(user.getUsername());
        loginUser.setPassword(user.getPassword());
        loginUser.setRealname(user.getRealname());
        loginUser.setAge(user.getAge());
        loginUser.setHeadPicture(user.getHeadPicture());
        loginUser.setLoginDeviceType(DeviceType.XCX);
        loginUser.setOpenid(openid);
        loginUser.setMenuPermission(permissionService.getUserMenuPermission(user.getId().toString()));
        loginUser.setRolePermission(permissionService.getUserRolePermission(user.getId().toString()));
        return loginUser;
    }

    /**
     * 根据code获取openid
     * @param xcxCode
     * @return 微信openid
     */
    private String getOpenid(String xcxCode) {
        try {
            String requestUrl = wechatAppletProperties.getOpenidUrl();         //小程序返回openid接口
            String appid = wechatAppletProperties.getAppid();                  //你自己的小程序appid
            String secret = wechatAppletProperties.getSecret();                //你自己的小程序秘钥
            String grant_type = wechatAppletProperties.getOpenidGrantType();    //默认的不要改
            String params = "appid=" + appid + "&secret=" + secret + "&js_code=" + xcxCode + "&grant_type=" + grant_type;
            //创建OkHttp客户端
            OkHttpClient okHttpClient = new OkHttpClient();
            String url = requestUrl + "?" + params;
            //构建请求（默认为get请求）
            Request request = new Request.Builder().url(url).build();
            //执行获取响应
            Response response = okHttpClient.newCall(request).execute();
            //取出数据
            String body = response.body().string();
            JSONObject json = JSONObject.parseObject(body);
            //判断是否成功
            Integer errcode = (Integer) json.get("errcode");
            if (errcode != null) {
                throw new ServiceException("获取微信openid失败",json);
            } else {
                //用户的唯一标识（openid）
                String openid = (String)json.get("openid");
                return openid;
            }
        }catch (Exception e){
            log.error(e.getMessage(),e);
            throw new ServiceException("获取微信openid出错！xcxCode="+xcxCode,e.getMessage());
        }
    }

    private LoginUser getUserInfo(String username,DeviceType deviceType) throws ServiceException {
        TbUser user = tbUserDAO.findByPropertyObject("username",username);
        if (ObjectUtil.isNull(user)) {
            throw new ServiceException("用户【"+username+"】不存在");
        }
        if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
            throw new ServiceException("用户【"+username+"】已被删除");
        }
        if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            throw new ServiceException("用户【"+username+"】已经停用");
        }
        LoginUser loginUser = new LoginUser();
        loginUser.setId(user.getId());
        loginUser.setUsername(user.getUsername());
        loginUser.setPassword(user.getPassword());
        loginUser.setRealname(user.getRealname());
        loginUser.setAge(user.getAge());
        loginUser.setHeadPicture(user.getHeadPicture());
        loginUser.setLoginDeviceType(deviceType);
        loginUser.setOpenid(user.getOpenid());
        loginUser.setMenuPermission(permissionService.getUserMenuPermission(user.getId().toString()));
        loginUser.setRolePermission(permissionService.getUserRolePermission(user.getId().toString()));
        return loginUser;
    }

    /**
     * 登录校验
     */
    private void checkLogin(String username, Supplier<Boolean> supplier, String deviceType) {
        Integer errorNumber = null;
        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
        Boolean pwdCheckEnable = userPasswordProperties.getPwdCheckEnable();
        Integer maxRetryCount = userPasswordProperties.getMaxRetryCount();
        Integer lockTime = userPasswordProperties.getLockTime();
        String loginFail = Constants.LOGIN_FAIL;
        String lockmsg = "登录失败次数过多，帐户已被锁定"+lockTime+"分钟";

        //开启登录失败次数检查时，在验证帐号密码前，先判断是否已经达到最大失败次数
        if(pwdCheckEnable!=null && pwdCheckEnable.booleanValue()){
            // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
            errorNumber = RedisUtils.getCacheObject(errorKey);
            // 锁定时间内登录 则踢出
            if (ObjectUtil.isNotNull(errorNumber) && errorNumber.intValue()>=maxRetryCount.intValue()) {
                recordLogininfor(username, loginFail, lockmsg, deviceType);
                throw new ServiceException(lockmsg);
            }
        }

        //登录失败
        if (supplier.get()) {
            String loginFailMsg = "帐号或密码错误，登录失败！";
            //开启登录失败次数检查时
            if(pwdCheckEnable!=null && pwdCheckEnable.booleanValue()){
                // 是否第一次
                errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
                // 达到规定错误次数 则锁定登录
                if (errorNumber.equals(maxRetryCount)) {
                    RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
                    recordLogininfor(username, loginFail, lockmsg, deviceType);
                    throw new ServiceException(lockmsg);
                } else {
                    // 未达到规定错误次数 则递增
                    loginFailMsg += "已登录失败"+errorNumber+"次！（失败"+maxRetryCount+"次后帐户将被锁定"+lockTime+"分钟）";
                    RedisUtils.setCacheObject(errorKey, errorNumber);
                    recordLogininfor(username, loginFail, loginFailMsg, deviceType);
                    throw new ServiceException(loginFailMsg);
                }
            }
            //未开启登录失败次数检查时
            else{
                throw new ServiceException(loginFailMsg);
            }
        }
        // 登录成功 清空错误次数
        RedisUtils.deleteObject(errorKey);
    }

    /**
     * 记录登录信息
     *
     * @param username 用户名
     * @param status   状态
     * @param message  消息内容
     * @return
     */
    @Override
    public void recordLogininfor(String username, String status, String message,String deviceType) {
        TbSysLogininfor logininfor = new TbSysLogininfor();
        logininfor.setInfoId(UserFun.getCurrTimeMillisLong());
        logininfor.setUsername(username);
        logininfor.setIpaddr(ServletUtils.getClientIP());
        logininfor.setMsg(message);
        logininfor.setDeviceType(deviceType);
        logininfor.setAccessTime(new Date());
        // 日志状态
        if (StrUtil.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
            logininfor.setStatus(Constants.LOGIN_SUCCESS_STATUS);
        } else if (Constants.LOGIN_FAIL.equals(status)) {
            logininfor.setStatus(Constants.LOGIN_FAIL_STATUS);
        }
        tbSysLogininforDAO.save(logininfor);
    }

    /**
     * 更新用户表中的最后登录时间和IP
     */
    private void updateUserLoginInfo(Integer userId){
        TbUser tbUser = tbUserDAO.findById(userId);
        tbUser.setLoginDate(new Date());
        tbUser.setLoginIp(ServletUtils.getClientIP());
        tbUserDAO.update(tbUser);
    }

    /**
     * 更新用户表中的最后登录时间和IP，并绑定openid
     */
    private void updateUserLoginInfoAndOpenid(Integer userId,String openid){
        TbUser tbUser = tbUserDAO.findById(userId);
        tbUser.setLoginDate(new Date());
        tbUser.setLoginIp(ServletUtils.getClientIP());
        tbUser.setOpenid(openid);
        tbUserDAO.update(tbUser);
    }
}
